﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using ActiproSoftware.SyntaxEditor;

namespace WindowsUserControl.Codes
{
    internal class SimpleLexicalParser:IMergableLexicalParser 
    {
        private bool caseSensitive;
        private Hashtable keywords = new Hashtable();

        /// <summary>
        /// Initializes a new instance of the <c>SimpleLexicalParser</c> class.
        /// </summary>
        /// <param name="caseSensitive">Whether the language is case sensitive.</param>
        public SimpleLexicalParser(bool caseSensitive)
        {
            // This is added for demo purposes... normally a language knows whether it is case sensitive or not
            this.caseSensitive = caseSensitive;

            // Initialize keywords
            for (int index = (int)SimpleTokenID.KeywordStart + 1; index < (int)SimpleTokenID.KeywordEnd; index++)
                keywords.Add(SimpleTokenID.GetTokenKey(index).ToLower(), index);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // PUBLIC PROCEDURES
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Returns a single-character <see cref="ITokenLexicalParseData"/> representing the lexical parse data for the
        /// default token in the <see cref="ILexicalState"/> and seeks forward one position in the <see cref="ITextBufferReader"/>
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <param name="lexicalState">The <see cref="ILexicalState"/> that specifies the current context.</param>
        /// <returns>The <see cref="ITokenLexicalParseData"/> for default text in the <see cref="ILexicalState"/>.</returns>
        public ITokenLexicalParseData GetLexicalStateDefaultTokenLexicalParseData(ITextBufferReader reader, ILexicalState lexicalState)
        {
            reader.Read();
            return new LexicalStateAndIDTokenLexicalParseData(lexicalState, (byte)lexicalState.DefaultTokenID);
        }

        /// <summary>
        /// Performs a lexical parse to return the next <see cref="ITokenLexicalParseData"/> 
        /// from a <see cref="ITextBufferReader"/> and seeks past it if there is a match.
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <param name="lexicalState">The <see cref="ILexicalState"/> that specifies the current context.</param>
        /// <param name="lexicalParseData">Returns the next <see cref="ITokenLexicalParseData"/> from a <see cref="ITextBufferReader"/>.</param>
        /// <returns>A <see cref="MatchType"/> indicating the type of match that was made.</returns>
        public MatchType GetNextTokenLexicalParseData(ITextBufferReader reader, ILexicalState lexicalState, ref ITokenLexicalParseData lexicalParseData)
        {
            // Initialize
            int tokenID = SimpleTokenID.Invalid;

            // Get the next character
            char ch = reader.Read();

            // If the character is a letter or digit...
            if ((Char.IsLetter(ch) || (ch == '_')))
            {
                // Parse the identifier
                tokenID = this.ParseIdentifier(reader, ch);
            }
            else if ((ch != '\n') && (Char.IsWhiteSpace(ch)))
            {
                while ((reader.Peek() != '\n') && (Char.IsWhiteSpace(reader.Peek())))
                    reader.Read();
                tokenID = SimpleTokenID.Whitespace;
            }
            else
            {
                tokenID = SimpleTokenID.Invalid;
                switch (ch)
                {
                    case ',':
                        tokenID = SimpleTokenID.Comma;
                        break;
                    case '(':
                        tokenID = SimpleTokenID.OpenParenthesis;
                        break;
                    case ')':
                        tokenID = SimpleTokenID.CloseParenthesis;
                        break;
                    case ';':
                        tokenID = SimpleTokenID.SemiColon;
                        break;
                    case '\n':
                        // Line terminator
                        tokenID = SimpleTokenID.LineTerminator;
                        break;
                    case '{':
                        tokenID = SimpleTokenID.OpenCurlyBrace;
                        break;
                    case '}':
                        tokenID = SimpleTokenID.CloseCurlyBrace;
                        break;
                    case '/':
                        tokenID = SimpleTokenID.Division;
                        switch (reader.Peek())
                        {
                            case '/':
                                // Parse a single-line comment
                                tokenID = this.ParseSingleLineComment(reader);
                                break;
                            case '*':
                                // Parse a multi-line comment
                                tokenID = this.ParseMultiLineComment(reader);
                                break;
                        }
                        break;
                    case '=':
                        if (reader.Peek() == '=')
                        {
                            reader.Read();
                            tokenID = SimpleTokenID.Equality;
                        }
                        else
                            tokenID = SimpleTokenID.Assignment;
                        break;
                    case '!':
                        if (reader.Peek() == '=')
                        {
                            reader.Read();
                            tokenID = SimpleTokenID.Inequality;
                        }
                        break;
                    case '+':
                        tokenID = SimpleTokenID.Addition;
                        break;
                    case '-':
                        tokenID = SimpleTokenID.Subtraction;
                        break;
                    case '*':
                        tokenID = SimpleTokenID.Multiplication;
                        break;
                    default:
                        if ((ch >= '0') && (ch <= '9'))
                        {
                            // Parse the number
                            tokenID = this.ParseNumber(reader, ch);
                        }
                        break;
                }
            }

            if (tokenID != SimpleTokenID.Invalid)
            {
                lexicalParseData = new LexicalStateAndIDTokenLexicalParseData(lexicalState, (byte)tokenID);
                return MatchType.ExactMatch;
            }
            else
            {
                reader.ReadReverse();
                return MatchType.NoMatch;
            }
        }

        /// <summary>
        /// Parses an identifier.
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <param name="ch">The first character of the identifier.</param>
        /// <returns>The ID of the token that was matched.</returns>
        protected virtual int ParseIdentifier(ITextBufferReader reader, char ch)
        {
            // Get the entire word
            int startOffset = reader.Offset - 1;
            while (!reader.IsAtEnd)
            {
                char ch2 = reader.Read();
                // NOTE: This could be improved by supporting \u escape sequences
                if ((!char.IsLetterOrDigit(ch2)) && (ch2 != '_'))
                {
                    reader.ReadReverse();
                    break;
                }
            }

            // Determine if the word is a keyword
            if (Char.IsLetter(ch))
            {
                object value;
                if (caseSensitive)
                    value = keywords[reader.GetSubstring(startOffset, reader.Offset - startOffset)];
                else
                    value = keywords[reader.GetSubstring(startOffset, reader.Offset - startOffset).ToLower()];

                if (value != null)
                    return (int)value;
                else
                    return SimpleTokenID.Identifier;
            }
            else
                return SimpleTokenID.Identifier;
        }

        /// <summary>
        /// Parses a multiple line comment.
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <returns>The ID of the token that was matched.</returns>
        protected virtual int ParseMultiLineComment(ITextBufferReader reader)
        {
            reader.Read();
            while (reader.Offset < reader.Length)
            {
                if (reader.Peek() == '*')
                {
                    if (reader.Offset + 1 < reader.Length)
                    {
                        if (reader.Peek(2) == '/')
                        {
                            reader.Read();
                            reader.Read();
                            break;
                        }
                    }
                    else
                    {
                        reader.Read();
                        break;
                    }
                }
                reader.Read();
            }
            return SimpleTokenID.MultiLineComment;
        }

        /// <summary>
        /// Parses a number.
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <param name="ch">The first character of the number.</param>
        /// <returns>The ID of the token that was matched.</returns>
        protected virtual int ParseNumber(ITextBufferReader reader, char ch)
        {
            while (Char.IsNumber(reader.Peek()))
                reader.Read();
            return SimpleTokenID.Number;
        }

        /// <summary>
        /// Parses a single line comment.
        /// </summary>
        /// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
        /// <returns>The ID of the token that was matched.</returns>
        protected virtual int ParseSingleLineComment(ITextBufferReader reader)
        {
            reader.Read();
            while ((!reader.IsAtEnd) && (reader.Peek() != '\n'))
                reader.Read();
            return SimpleTokenID.SingleLineComment;
        }
    }
}
